/*
GANESHA - Reasoning on critical infrastructures affected by emergency situations
Copyright (c) 2010-2011, Information Eng. Research Unit - Univ. of Alcala
http://www.cc.uah.es/ie
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option)
any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along
with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package es.uah.cc.ie.ganesha;

import com.esri.arcgis.geodatabase.IFeature;
import com.esri.arcgis.geodatabase.IFeatureCursor;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.ObjectProperty;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import es.uah.cc.ie.ganesha.gis.GISManager;
import es.uah.cc.ie.ganesha.messaging.AlertMessage;
import es.uah.cc.ie.ganesha.messaging.position.PointPosition;
import es.uah.cc.ie.ganesha.nodes.NodeInfo;
import es.uah.cc.ie.ganesha.ontologies.PersistentOntology;
import es.uah.cc.ie.ganesha.results.GANESHAResultEntry;
import es.uah.cc.ie.ganesha.results.GANESHAResults;
import es.uah.cc.ie.ganesha.sahana.SAHANAReport;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents the main GANESHA runtime, which holds one or several situation
 * managers.
 *
 * @author ieru
 */
public class GANESHAModel extends Observable {

    List<SituationManager> _situations;
    PersistentOntology _ontologies;
    String _name;
    GANESHAConfiguration _configuration;
    boolean _useGIS = false;

    /**
     * Constructor.
     * @param name Name of the model.
     * @param cfg Configuration.
     */
    public GANESHAModel(String name, GANESHAConfiguration cfg) {
        _situations = new ArrayList<SituationManager>();

        _configuration = cfg;
        _name = name;
        _ontologies = new PersistentOntology(cfg.getParams(), cfg.getOntologies(), false);

		//Disable Pellet warnings
		Logger.getLogger("org.mindswap.pellet.jena.graph.loader.DefaultGraphLoader").setLevel(Level.SEVERE);

        String gisDataSource = (String) cfg.getParams().get("GANESHA_GIS_DATA_SOURCE");

        if (gisDataSource != null) {
            try {
                GISManager.getInstance().addDataSource(gisDataSource);
                _useGIS = true;
            } catch (IOException ex) {
                Logger.getLogger(GANESHAModel.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Adds a situation manager to the list.
     * @param situationManager Situation manager to be added.
     */
    public void addSituation(SituationManager situationManager) {
        _situations.add(situationManager);
    }

    /**
     * Returns the list of situation managers.
     * @return List of situation managers.
     */
    public Collection<SituationManager> getSituations() {
        return _situations;
    }

    /**
     * Infers information from all the situations.
     * @return Returns the list of messages inferred by the reasoner.
     */
    public GANESHAResults infer() {
        assert (_situations.size() == 1);
        GANESHAResults result = new GANESHAResults();

        OntModel ontModel = _ontologies.getOntologyModel();

        //om.begin();

        //HACER UN ONTMODEL.BEGIN PARA INICIAR LA TRANSACCI�N

        OntClass nodeClass = ontModel.getOntClass("http://www.ieru.org/ont/emergency/BFiaO#Node");
        ObjectProperty hasAffectedElement = ontModel.getObjectProperty("http://www.ieru.org/ont/emergency/BFiaO#hasAffectedElement");

        List<Individual> nodeList = ontModel.listIndividuals(nodeClass).toList();

        //Process every situation
        for (SituationManager situation : _situations) {
            Collection<AlertMessage> messageList = situation.getMessages().values();

            for (AlertMessage message : messageList) {
                //Adds the incoming alert message directly to the results (as real incidents)
                GANESHAResultEntry<AlertMessage> real_entry = new GANESHAResultEntry<AlertMessage>();
                real_entry.message = message;
                real_entry.nodes = new ArrayList<NodeInfo>();
                if (message.isUnique()) {
                    result.getRealIncidents().setUniqueIncident(real_entry);
                } else {
                    result.getRealIncidents().getIncidents().add(real_entry);
                }
				processMessage(message, nodeList, ontModel, hasAffectedElement, situation, result);
            }
        }

		if ("1".equals(_configuration.getParams().get("SAHANA_REPORT_RESULTS"))) {
            SAHANAReport.sendResultsToSAHANA(_configuration, result);
        }

        return result;
    }

	/**
	 * Processes a single message
	 * @param message Message to be processed
	 * @param nodeList List of nodes
	 * @param ontModel Ontology model
	 * @param hasAffectedElement hasAffectedElement property
	 * @param situation Situation manager
	 * @param result Result object to be modified
	 */
	private void processMessage(AlertMessage message, List<Individual> nodeList,
			OntModel ontModel, ObjectProperty hasAffectedElement,
			SituationManager situation, GANESHAResults result) {

		GANESHAResultEntry<AlertMessage> potential_entry;
		List<NodeInfo> uniqueMessageNodes = new ArrayList<NodeInfo>();

		//Check nearby nodes
		List<Individual> initiallyAffectedNodes = AffectedNodeSearch.getNearbyNodes(message, nodeList);

		//If it's a unique message, we get all the submessages to see what nodes do they affect
		if(message.isUnique())
		{
			for(AlertMessage submessage : message.getUniqueSubMessages())
			{
				List<Individual> submsgAffectedNodes = AffectedNodeSearch.getNearbyNodes(submessage, nodeList);

				for(Individual node : submsgAffectedNodes)
					if(!initiallyAffectedNodes.contains(node))
						initiallyAffectedNodes.add(node);
			}
		}
		
		//Add information to the ontology about how the alert message affects nearby nodes
		if (initiallyAffectedNodes.size() > 0) {
			Individual event = addEvent(ontModel, message.getIdentifier(), message.getIdentifier(), message.getCenterLatitude(), message.getCenterLongitude());

			for (Individual affectedNode : initiallyAffectedNodes)
			{
				//Begin the transaction so a rollback can be done
				if (ontModel.supportsTransactions()) {
					ontModel.begin();
				}

				//Set the node as affected by the message
				event.addProperty(hasAffectedElement, affectedNode);
				
				//Let the reasoner infer new affected nodes
				ontModel.prepare();


				String eventTypeString = "";
				List<NodeInfo> affectedNodes = new ArrayList<NodeInfo>();

				//Analyze new affected nodes
				for (Iterator i = event.listPropertyValues(hasAffectedElement); i.hasNext();) {
					Resource c = (Resource) i.next();

					NodeInfo node = getInfoFromNode((Individual) ontModel.getIndividual(c.toString()));
					eventTypeString = situation.getEventTypeFromDomain(node.getDomain());
					affectedNodes.add(node);

					if (_useGIS) {
						List<NodeInfo> nearbyUnpassableRoads = getUnpassableRoads(node);
						for (NodeInfo ni : nearbyUnpassableRoads) {
							ni.setLat(node.getLat());
							ni.setLon(node.getLon());
						}
						affectedNodes.addAll(nearbyUnpassableRoads);
					}
				}
				
				/*if(!message.isUnique())
				{*/
					//The coordinates of the message are the same as the mainly affected node
					NodeInfo mainNode = getInfoFromNode(affectedNode);
					double lat = mainNode.getLat();
					double lon = mainNode.getLon();
					
					potential_entry = new GANESHAResultEntry<AlertMessage>();
					//SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
					//String date = df.format(Calendar.getInstance().getTime().getTime());
					potential_entry.message = situation.newMessage(_name + "_" + message.getIdentifier() + "_" + Calendar.getInstance().getTime().getTime());
					potential_entry.nodes = affectedNodes;

					//if(message.isUnique())
					//	potential_entry.message.setUniqueSubMessages(message.getUniqueSubMessages());

					potential_entry.message.addPosition(new PointPosition(lat, lon));
					potential_entry.message.setEventType(eventTypeString);
					potential_entry.message.setPriority(2);
					result.getPotentialIncidents().getIncidents().add(potential_entry);
				/*} else
				{
					//Only add nodes if they where not previously added
					for(NodeInfo node : affectedNodes)
						if(!uniqueMessageNodes.contains(node))
							uniqueMessageNodes.add(node);
				}*/

				event.removeAll(hasAffectedElement);
				
				//Abort the transaction
				if (ontModel.supportsTransactions()) {
					ontModel.abort();
				}
			}

			/*if(message.isUnique())
			{
				potential_entry = new GANESHAResultEntry<AlertMessage>();
				//SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
				//String date = df.format(Calendar.getInstance().getTime().getTime());
				potential_entry.message = situation.newMessage(_name + "_" + message.getIdentifier() + "_" + Calendar.getInstance().getTime().getTime());
				potential_entry.nodes = uniqueMessageNodes;

				potential_entry.message.setUniqueSubMessages(message.getUniqueSubMessages());
				potential_entry.message.setPriority(2);
				result.getPotentialIncidents().setUniqueIncident(potential_entry);
			}*/
		}
	}

	    /**
     * Adds an event to the ontology.
     * @param ontology Ontology where the event will be added.
     * @param eventName Event name.
     * @param positionName Position instance's name.
     * @param lat Latitude where the event took place.
     * @param lng Longitude where the event took place.
     * @return The individual that was created.
     */
    private Individual addEvent(OntModel ontology, String eventName, String positionName, double lat, double lng) {
        OntClass eventClass = ontology.getOntClass("http://www.ieru.org/ont/emergency/BFiaO#Event");
        OntClass positionClass = ontology.getOntClass("http://www.ieru.org/ont/emergency/BFiaO#Position");
        OntClass coordinateClass = ontology.getOntClass("http://www.ieru.org/ont/emergency/BFiaO#Coordinate");

        Literal latLiteral = ontology.createTypedLiteral(lat);
        Literal lngLiteral = ontology.createTypedLiteral(lng);
        Literal idLiteral = ontology.createTypedLiteral(eventName);

        Individual coordinate = coordinateClass.createIndividual("http://www.ieru.org/ont/emergency/enagas#Coordinate_" + positionName);
        coordinate.setPropertyValue(ontology.getProperty("http://www.ieru.org/ont/emergency/enagas#lat"), latLiteral);
        coordinate.setPropertyValue(ontology.getProperty("http://www.ieru.org/ont/emergency/enagas#long"), lngLiteral);

        Individual position = coordinateClass.createIndividual("http://www.ieru.org/ont/emergency/enagas#Position_" + positionName);
        position.setPropertyValue(ontology.getProperty("http://www.ieru.org/ont/emergency/enagas#hasCoordinate"), coordinate);

        Individual event = eventClass.createIndividual("http://www.ieru.org/ont/emergency/enagas#Event_" + eventName);
        event.setPropertyValue(ontology.getProperty("http://www.ieru.org/ont/emergency/enagas#hasLocation"), position);
        event.setPropertyValue(ontology.getProperty("http://www.ieru.org/ont/emergency/BFiaO#identifier"), idLiteral);

        return event;
    }
    private static final String HAS_LOCATION_PROP_URI = "http://www.ieru.org/ont/emergency/BFiaO#hasLocation";
    private static final String HAS_COORDINATE_PROP_URI = "http://www.ieru.org/ont/emergency/BFiaO#hasCoordinate";
    private static final String LAT_PROP_URI = "http://www.ieru.org/ont/emergency/BFiaO#lat";
    private static final String LON_PROP_URI = "http://www.ieru.org/ont/emergency/BFiaO#lon";

    /**
     * Obtains info from a given node in the ongoloty.
     * @param node Individual in the ontology where the info will be obtained from.
     * @return Info from the given node in the ongoloty.
     */
    private NodeInfo getInfoFromNode(Individual node) {
        NodeInfo info = new NodeInfo();

        OntModel ontology = node.getOntModel();

        Property hasLocationProp = ontology.getProperty(HAS_LOCATION_PROP_URI);
        Property hasCoordinateProp = ontology.getProperty(HAS_COORDINATE_PROP_URI);
        Property latProp = ontology.getProperty(LAT_PROP_URI);
        Property lonProp = ontology.getProperty(LON_PROP_URI);

        info.setDomain(getDomainFromNode(node)); //TODO: a�adir informacion real del dominio!!!

        RDFNode hasLocationValue = node.getPropertyValue(hasLocationProp);
        Individual nodeLocation = (Individual) ontology.getIndividual(hasLocationValue.toString());

        RDFNode hasCoordinateValue = nodeLocation.getPropertyValue(hasCoordinateProp);
        Individual nodeCoordinate = (Individual) ontology.getIndividual(hasCoordinateValue.toString());

        info.setLat(nodeCoordinate.getProperty(latProp).getDouble());
        info.setLon(nodeCoordinate.getProperty(lonProp).getDouble());

        info.setName(node.getLocalName());
        info.setId(info.getName());

        return info;
    }

    private static final String BELONGS_TO_DOMAIN_PROP_URI = "http://www.ieru.org/ont/emergency/BFiaO#belongsToDomain";
    private static final String DOMAIN_HAS_NAME_PROP_URI = "http://www.ieru.org/ont/emergency/BFiaO#hasName";

	/**
     * Obtains a descriptive string from the node domain.
     * @param node Node to be checked.
     * @return Descriptive string from the node domain.
     */
    private String getDomainFromNode(Individual node) {
		OntModel _ontologyModel = _ontologies.getOntologyModel();

        final Property belongsToDomainProperty = _ontologyModel.getObjectProperty(BELONGS_TO_DOMAIN_PROP_URI);
		final Property domainNameProperty = _ontologyModel.getDatatypeProperty(DOMAIN_HAS_NAME_PROP_URI);

		String domainURI = node.getPropertyValue(belongsToDomainProperty).toString();
		Individual domainNode = (Individual) _ontologyModel.getIndividual(domainURI);
		String domainName = domainNode.getPropertyValue(domainNameProperty).asLiteral().getString();

        return domainName;
    }

    /**
     * Checks all the roads near (< 10km) a node and returns all the nearby roads that aren't passable.
     * @param node Node around of which the checks will be performed.
     * @return Returns all the nearby roads that aren't passable.
     */
    private List<NodeInfo> getUnpassableRoads(NodeInfo node) {
        List<NodeInfo> result = new ArrayList<NodeInfo>();

        try {
            //Look for roads in 10km of distance from the node
            IFeatureCursor cursor = GISManager.getInstance().getLayer("Carreteras").distanceQuery(node.getLat(), node.getLon(), 0.2);

            IFeature road = cursor.nextFeature();

            while (road != null) {

                int passableIndex = road.getFields().findField("PASSABLE");

                if (road.getValue(passableIndex).equals(Short.valueOf((short) 0))) {
                    int nameIndex = road.getFields().findField("NAME");

                    NodeInfo roadNode = new NodeInfo();

                    roadNode.setDomain(/*"TECNOLOGICO/INFRAESTRUCTURAS/*/"CARRETERAS");
                    roadNode.setName(road.getValue(nameIndex).toString());

                    result.add(roadNode);
                }

                road = cursor.nextFeature();
            }
        } catch (IOException ex) {
            Logger.getLogger(GANESHAModel.class.getName()).log(Level.SEVERE, null, ex);
        }

        return result;
    }

}
